﻿using System;
using System.Data;
using System.Configuration;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Security.Policy;
using System.IO;
using System.Collections.Generic;

namespace net.veekee.common
{
    public class DynamicClass
    {
        private static String compiledDirectory = "";
        public static List<String> DefaultBusinessImports = new List<String>();

        public bool GenerateFileAssembly
        {
            get;
            set;
        }
        public String GeneratedFile
        {
            get
            {
                return compiledDirectory + "\\" + this.NameSpace + "." + this.ClassName + ".dll";
            }
        }

        public DynamicClassStatus Status
        {
            get;
            set;
        }

        protected String sourceCode = null;
        public String SourceCode
        {
            get
            {
                return this.sourceCode;
            }
        }

        protected Assembly CompiledAssembly = null;

        protected Type type = null;
        public Type Type
        {
            get
            {
                return this.type;
            }
        }

        protected String NameSpace = "net.veekee.rules";
        protected Dictionary<String, String> References = new Dictionary<String, String>();
        protected Dictionary<String, String> CustomReferences = new Dictionary<String, String>();
        protected List<String> Imports = new List<String>();
        protected Dictionary<String, String> AssemblyOptions = new Dictionary<String, String>();
        protected String className = "";
        public String ClassName
        {
            get
            {
                return this.className;
            }
        }
        protected String Inherited = "";
        protected StringBuilder sourceTemplate = null;
        protected StringBuilder sourceAddOn = null;

        static DynamicClass()
        {
            DynamicClass.compiledDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\compiled";
            if (!System.IO.Directory.Exists(DynamicClass.compiledDirectory))
                System.IO.Directory.CreateDirectory(DynamicClass.compiledDirectory);
        }

        public DynamicClass(String name)
        {
            this.className = name;
            this.sourceTemplate = new StringBuilder();
            this.sourceAddOn = new StringBuilder();

            this.createTemplate();
        }

        protected void AddAssemblyOption(String option, String optionValue)
        {
            if (this.AssemblyOptions.ContainsKey(option))
                return;
            this.AssemblyOptions.Add(option, optionValue);
        }

        protected void AddImport(String import)
        {
            if (import.Length == 0)
                return;
            if (this.Imports.Contains(import))
                return;
            this.Imports.Add(import);
        }

        protected void AddReference(String identity, String reference, bool custom = false)
        {
            if (identity.Contains("mscorlib"))
                return;
            if (custom)
            {
                if (this.CustomReferences.ContainsKey(identity))
                    return;
                Log.GetInstance().Trace("Referencing custom assembly {0} from {1}", identity, reference);
                this.CustomReferences.Add(identity, reference);
            }
            else
            {
                if (this.References.ContainsKey(identity))
                    return;
                Log.GetInstance().Trace("Referencing assembly {0} from {1}", identity, reference);
                this.References.Add(identity, reference);
            }
        }

        protected void sourceCodeAddLine(StringBuilder sb, String line)
        {
            sb.Append(line + "\n\n");
        }

        private void createTemplate()
        {
            this.AddImport("System");
            this.AddImport("System.Collections.Generic");
            this.AddImport("System.Xml");
            this.AddImport("System.CodeDom");
            this.AddImport("System.CodeDom.Compiler");
            this.AddImport("System.Reflection");
            this.sourceTemplate.Append(@"
{1}

namespace {0}
{{
    public class {2}{3}
    {{
        {4}
    }}
}}
");
            foreach (String businessImport in DynamicClass.DefaultBusinessImports)
            {
                this.AddImport(businessImport);
            }

            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
            {
                this.AddReference(a.ToString(), a.Location);
            }
        }

        public virtual void Compile()
        {
            try
            {
                if (this.Status == DynamicClassStatus.Rejected)
                {
                    Log.GetInstance().Info("Dynamic class {0} has been flaged as rejected", this.ClassName);
                    return;
                }

                Log.GetInstance().Debug("Compiling a new dynamic class: {0}", this.ClassName);

                this.Status = DynamicClassStatus.Unknown;

                Dictionary<String, String> providerOptions = new Dictionary<String, String>();
                providerOptions.Add("CompilerVersion", "v4.0");
                CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
                CompilerParameters parameters = new CompilerParameters();
                CompilerResults results = null;

                foreach (String r in this.References.Values)
                {
                    Log.GetInstance().Trace("Adding assembly {0}", r);
                    parameters.ReferencedAssemblies.Add(r);
                }

                parameters.CompilerOptions = "/t:library";
                parameters.GenerateInMemory = !this.GenerateFileAssembly;
                parameters.OutputAssembly = (this.GenerateFileAssembly ? this.GeneratedFile : "");
                parameters.GenerateExecutable = false;
#if DEBUG
                parameters.IncludeDebugInformation = true;
#else
                parameters.IncludeDebugInformation = false;
#endif

                StringBuilder sbImports = new StringBuilder();
                foreach (String import in this.Imports)
                    sbImports.AppendLine("using " + import + ";");

                foreach (KeyValuePair<String, String> option in this.AssemblyOptions)
                    sbImports.AppendLine("[assembly: " + option.Key +" (\"" + option.Value + "\")]");

                this.sourceCode = String.Format(this.sourceTemplate.ToString(), this.NameSpace, sbImports.ToString(), this.ClassName, (this.Inherited.Length > 0 ? " : " + this.Inherited : ""), this.sourceAddOn.ToString());

#if DEBUG
                System.IO.File.WriteAllText(this.GeneratedFile + ".txt", this.sourceCode);
#endif

                results = provider.CompileAssemblyFromSource(parameters, this.sourceCode);

                if (results.Errors.Count != 0)
                {
                    Log.GetInstance().Error("Result: Invalid");
                    foreach (System.CodeDom.Compiler.CompilerError error in results.Errors)
                        Log.GetInstance().Error(error.ToString());
                    this.Status = DynamicClassStatus.Invalid;
                    return;
                }

                this.Status = DynamicClassStatus.Valid;
                this.CompiledAssembly = results.CompiledAssembly;
                this.type = results.CompiledAssembly.GetType(this.NameSpace + "." + this.ClassName);
                Log.GetInstance().Debug("Result: Valid ({0})", this.NameSpace + "." + this.ClassName);
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Result: Invalid ({0})", e.ToString());
                this.Status = DynamicClassStatus.Invalid;
            }
        }

        public Object Create()
        {
            if (this.CompiledAssembly == null)
                return null;
            return this.CompiledAssembly.CreateInstance(this.NameSpace + "." + this.ClassName);
        }
    }

    public enum DynamicClassStatus
    {
        Unknown,
        Valid,
        Invalid,
        Empty,
        Rejected
    }
}
